#include <unittest++/UnitTest++.h>
#include <Ape/Collections/Array.h>
#include <Ape/IO/FS/RegularFile.h>
#include <Ape/Exception.h>
#include <Ape/IO/FS/EFileNotFound.h>
#include <Ape/EInvalidOperation.h>
#include <Ape/EIndexOutOfRange.h>
#include <Ape/Serialization/ISerializeable.h>
#include <Ape/IO/Console.h>
#include <Ape/Int.h>

#define TEMP_DIR L"/tmp"


namespace ApeTest {
	namespace Serialization {
		using namespace Ape::IO::FS;
		using Ape::Collections::Array;
		
		/**
		 * Here we are going to sink some ints and floats into the file
		 * Then we want to get them back in the same order reading the file
		 */
		TEST (Ape_IO_FS_RegularFile__TypedWritingAndReading) {
			RegularFile fw (TEMP_DIR L"/Ape_IO_FS_RegularFile__TypedWritingAndReading.tmp");
			RegularFile fr (TEMP_DIR L"/Ape_IO_FS_RegularFile__TypedWritingAndReading.tmp");
			fw.Open (oWriteBinary);
			fr.Open (oReadBinary);
			
			for (int i = 0; i < 128; i++) {
				CHECK_EQUAL (sizeof (int), fw.WriteTyped<int> (i) );
			}
			fw.Flush();
			for (int i = 0; i < 128; i++) {
				CHECK_EQUAL (i, fr.ReadTyped<int>() );
			}
			CHECK_THROW (fr.ReadTyped<int>(), Ape::EIndexOutOfRange);
			fw.Unlink();
			
			fw.Open (oWriteBinary);
			fr.Open (oReadBinary);
			for (float f = 0.0; f < 1.0; f += 0.01) {
				CHECK_EQUAL (sizeof (float), fw.WriteTyped<float> (f) );
			}
			fw.Flush();
			for (float f = 0.0; f < 1.0; f += 0.01) {
				CHECK_EQUAL (f, fr.ReadTyped<float>() );
			}
			CHECK_THROW (fr.ReadTyped<float>(), Ape::EIndexOutOfRange);
			fw.Unlink();
		}
		
		TEST (Ape_IO_FS_RegularFile__TestSinking) {
			RegularFile sink (TEMP_DIR L"/Ape_IO_FS_RegularFile__TestSinking.tmp");
			
			/*
			 * First we will try to put and restore the following: int(123), float(12.3), char[4]("123");
			 */
			sink.Open (oWriteBinary);
			sink.SubmergeTyped<int> (123);
			sink.SubmergeTyped<float> (12.0);
			sink.SubmergeTyped<float> (12.3);
			sink.SubmergeTyped < const char[4] > ("123");
			
			sink.Close();
			
			sink.Open (oReadBinary);
			int i = 0;
			float f1 = 0.0, f2 = 0.0;
			char ch[4] = "   ";
			sink.EmergeTyped<int> (i);
			sink.EmergeTyped<float> (f1);
			sink.EmergeTyped<float> (f2);
			sink.EmergeTyped < char[4] > (ch);
			
			CHECK_EQUAL (123, i);
			CHECK_EQUAL (12.0, f1);
			//CHECK_EQUAL(12.3, f2); // XXX: How do we loose the precision?
			CHECK_EQUAL (Ape::String (ch), Ape::String ("123") );
			
			sink.Unlink();
		}
		
		
		class TestSerialMember : public Ape::Serialization::ISerializeable {
		public:
			int i;
			TestSerialMember() : i (0) {}
			virtual ~TestSerialMember() {}
			
			virtual void Sink (Ape::Serialization::ISink* sink) const {
				sink->SubmergeTyped<int> (i);
			}
			virtual void Unsink (Ape::Serialization::ISink* sink) {
				sink->EmergeTyped<int> (i);
			}
		};
		
		class TestSerial
					: public Ape::Object,
					public Ape::Serialization::ISerializeable
		{
		public:
			int i1;
			int i2;
			TestSerialMember* m_pM;
			TestSerialMember m_M;
			
			TestSerial () : i1 (0), i2 (0), m_pM (NULL) {}
			virtual ~TestSerial() { del (m_pM); }
			
			virtual void Sink (Ape::Serialization::ISink* sink) const {
				//printf("Sinking...\n");
				sink->SubmergeTyped<int> (i1);
				sink->SubmergeTyped<int> (i2);
				sink->Submerge (m_M);
				sink->Submerge(m_pM);
				//printf("Sunk...\n");
			}
			virtual void Unsink (Ape::Serialization::ISink* sink) {
				//printf("Unsinking...\n");
				sink->EmergeTyped<int> (i1);
				sink->EmergeTyped<int> (i2);
				sink->Emerge (m_M);
				sink->Emerge(m_pM);
				//printf("Unsunk...\n");
			}
			
			virtual void _Dump (bool verbose = false) {
				Object::_Dump (verbose);
				
				DUMP_NON_V_TYPE;
				
				using Ape::IO::StdOut;
				using Ape::IO::NewLine;
				
				StdOut
				<< L"\t\ti1:\t" << Ape::Int (i1).ToString () << NewLine
				<< L"\t\ti2:\t" << Ape::Int (i2).ToString () << NewLine;
				
			}
		};
		
		TEST (Ape_IO_FS_RegularFile__TestSerialization) {
			RegularFile sink (TEMP_DIR L"/Ape_IO_FS_RegularFile__TestSerialization.tmp");
			TestSerial s1;
			s1.i1 = 123;
			s1.i2 = 321;
			s1.m_M.i = 484;
			
			sink.Open (oWriteBinary);
			try {
				sink.Submerge (s1);
			}
			catch (Ape::Exception& exc) {
				exc._Dump (true);
			}
			sink.Close();
			
			TestSerial s2;
			s2.i1 = 0;
			s2.i2 = 0;
			sink.Open (oReadBinary);
			
			/*
			printf ("%i <-> %i\n", s1.i1, s2.i1);
			printf ("%i <-> %i\n", s1.i2, s2.i2);
			printf ("%i <-> %i\n", s1.m_M.i, s2.m_M.i);
			*/
			
			try {
				sink.Emerge (s2);
			}
			catch (Ape::Exception& exc) {
				exc._Dump (true);
			}
			CHECK_EQUAL (s1.i1, s2.i1);
			CHECK_EQUAL (s1.i2, s2.i2);
			CHECK_EQUAL (s1.m_M.i, s2.m_M.i);
			
			/*
			printf ("%i <-> %i\n", s1.i1, s2.i1);
			printf ("%i <-> %i\n", s1.i2, s2.i2);
			printf ("%i <-> %i\n", s1.m_M.i, s2.m_M.i);
			*/
		}
	}
}
#undef TEMP_DIR
